package file

import (
	"github.com/pkg/errors"
	"os"
	"strconv"
	"sync"
	"time"
	"xj.com/oos-data-service/dto"
	"xj.com/oos-data-service/error_handler"
	"xj.com/oos-data-service/utils"
)

// 分隔符的加入是以防万一元数据ES数据不保，还可以通过解析文件的方式，还原ES数据
var objectSeparetor []byte

const (
	daysTimeFormat     = "20060102" //用于把当前时间转换为数字
	objectSeparatorLen = 8          //对像分隔符的长度
)

type FileService struct {
	WorkPath            string     //文件夹
	Size                int        //兆
	writeLock           sync.Mutex //同一时间只允许一个请求写文件
	currentFile         *os.File   //当前工作的文件对像，为了减速少IO的次数，采用一直持有文件对像的方式。假定程序完全退出时会自动释放
	currentFileFullName string     //当前工作文件的完整的文件名
	currentFileSize     int64      //当前工作文件的尺寸
	day                 int64      //当前工作文件夹换算成天的值，即每天一个文件夹
}

func New(workPath string, fileSize int) (s *FileService) {
	objectSeparetor = make([]byte, 8)

	s = new(FileService)
	s.WorkPath = workPath
	s.Size = fileSize
	s.day = getCurrentDay()
	s.writeLock = sync.Mutex{}
	s.changeWorkFile()
	return s
}

func (s *FileService) Write(fileInfo dto.FileInfo) (info dto.MetaInfo) {
	defer s.writeLock.Unlock()
	s.writeLock.Lock()

	if s.needToChangeWorkFile() {
		s.releaseCurrentFileObj()
		s.changeWorkFile()
	}
	start := s.currentFileSize
	if s.currentFileSize > 0 {
		start = start + int64(objectSeparatorLen)
	}
	//追加内容到文件结尾
	info = dto.MetaInfo{
		Id:               fileInfo.ObjectId,
		Start:            start,
		End:              start + int64(len(fileInfo.Payload)), //当前文件总长度+对像长度+分隔符长度
		FileName:         s.currentFileFullName,
		ObjectCreateTime: fileInfo.CreateTime,
	}
	//如果文件已经有内容就需要写入分隔再写对像
	if s.currentFileSize > 0 {
		//先写分隔字节
		_, err := s.currentFile.Write(objectSeparetor)
		error_handler.HttpPanicIfError(errors.WithStack(err))
		//更新当前文件的大小
		s.currentFileSize = s.currentFileSize + int64(len(objectSeparetor))
	}

	//再写真正的对像
	_, err := s.currentFile.Write(fileInfo.Payload)
	error_handler.HttpPanicIfError(errors.WithStack(err))

	//更新当前文件的大小
	s.currentFileSize = s.currentFileSize + int64(len(fileInfo.Payload))
	return info
}

func (*FileService) Read(metaInfo dto.MetaInfo) []byte {
	bigFile, err := os.Open(metaInfo.FileName)
	if bigFile != nil {
		defer bigFile.Close()
	}
	error_handler.HttpPanicIfError(err)
	var cache = make([]byte, metaInfo.End-metaInfo.Start)
	_, err = bigFile.ReadAt(cache, metaInfo.Start)
	error_handler.HttpPanicIfError(err)

	return cache

}

/*
确保工作的文件路径，对像，Size完全正确
*/
func (s *FileService) needToChangeWorkFile() bool {
	//如果当前文件的尺寸已经超过最大值，需要切换文件
	if s.currentFileSize >= int64(s.Size*1024*1024) {
		return true
	}
	i := getCurrentDay()
	if i > s.day {
		return true
	}

	return false
}

/*
释放当前文件持有
重置当前文件大小为0
重置当前工作的日期为当前时间
*/
func (s *FileService) releaseCurrentFileObj() {
	if s.currentFile != nil {
		_ = s.currentFile.Close()
	}
	s.currentFileSize = 0
	s.day = getCurrentDay()
}

/*
重置当前工作的文件名
重置当前工作的文件对像
*/
func (s *FileService) changeWorkFile() {
	dirName := time.Now().Format(daysTimeFormat)
	fileName := utils.GetId()
	dirPath := s.WorkPath + string(os.PathSeparator) + dirName
	_ = os.MkdirAll(dirPath, os.ModeDir)
	s.currentFileFullName = s.WorkPath + string(os.PathSeparator) + dirName + string(os.PathSeparator) + fileName
	f, err := os.OpenFile(s.currentFileFullName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	error_handler.HttpPanicIfError(errors.WithStack(err))
	s.currentFile = f
}

/*
静默创建文件夹
*/
func (s *FileService) ensureDirExists(path string) error {
	_, err := os.Stat(path)
	if err == nil || !os.IsNotExist(err) {
		return nil
	}

	//不存在，则创建
	err = os.MkdirAll(path, os.ModeDir)
	if err != nil {
		return errors.WithMessage(err, "创建文件夹："+path+"失败。")
	}
	return nil
}

/*
取当前时间对应的年月日
直接年月日进行拼接，并转为整数，方便进行比较
*/
func getCurrentDay() int64 {
	//时间，如果当前工作的文件夹时间已经发生改变，需要切换文件
	daysTmp := time.Now().Format(daysTimeFormat)
	i, err := strconv.ParseInt(daysTmp, 10, 64)
	error_handler.HttpPanicIfError(err)
	return i
}
